Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 67994db6fd4c89a1823236e8df652a0417d7c9a1


Parents : adcb298
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-07-08T16:10:53-05:00

feat(Self-Test): enhance self-check functionality with expanded diagnostics for identity, critical imports, storage lock, temp filesystem, public assets, LXMF router, subprocess spawn, and run-module re-entry. Update UI to reflect new checks and improve localization support across multiple languages.

Changes
Diff

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 52521c82..1c27e711 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ All notable changes to this project will be documented in this file.
- **Bots / macOS**: Creating or starting LXMFy bots from a frozen desktop build no longer re-launches a second MeshChatX instance (and hit the storage lock). Bot subprocesses re-enter ``bot_process`` via ``--meshchatx-run-module``.
- **Self-Test**: Diagnostics now include a live **bot create / start / stop / delete** check (also covered by ``--self-check`` CI and E2E smoke).
+- **Self-Test**: Expanded cross-platform checks for identity, critical imports, storage lock, temp filesystem, public assets, LXMF router, subprocess spawn, and ``--meshchatx-run-module`` re-entry (Windows / macOS / Linux CI).
- **RNSh / Windows**: Frozen desktop builds no longer launch rnsh via ``python -m`` (``sys.executable`` is MeshChatX itself and rejects ``-m``). Sessions re-enter the bundled rnsh module with ``--meshchatx-run-module``.
- **CI / nightly**: Daily ``nightly-YYYY.MM.DD-<sha>`` tags from ``dev`` now explicitly ``workflow_dispatch`` ``build-release.yml`` after tagging so full release assets are produced.
- **Plugins**: Plugin worker `postRequest` Promise wrapper, plugin locale loading at boot, cached UI on page open, and slot renderer recursion for nested column/list/row children.

diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py
index beb35e97..179fab81 100644
--- a/meshchatx/meshchat.py
+++ b/meshchatx/meshchat.py
@@ -938,7 +938,33 @@ class ReticulumMeshChat:
rw_ok = False
rw_reason = f"Read/write test failed: {str(e)}"
+ from meshchatx.src.backend import self_check as self_check_mod
+
bots_ok, bots_reason = self._check_bot_lifecycle()
+ identity_result = self_check_mod.check_identity(self.identity)
+ imports_result = self_check_mod.check_critical_imports()
+ # Fold Python runtime into imports so the UI stays one row.
+ runtime_result = self_check_mod.check_python_runtime()
+ if runtime_result["status"] != "ok" and imports_result["status"] == "ok":
+ imports_result = runtime_result
+ elif runtime_result["status"] != "ok":
+ imports_result = {
+ "status": "failed",
+ "reason": f"{imports_result.get('reason') or ''} | {runtime_result.get('reason') or ''}".strip(
+ " |"
+ ),
+ }
+ storage_lock_result = self_check_mod.check_storage_lock(
+ self.storage_path or self.storage_dir
+ )
+ temp_fs_result = self_check_mod.check_temp_filesystem()
+ public_assets_result = self_check_mod.check_public_assets(self.get_public_path)
+ lxmf_result = self_check_mod.check_lxmf_router(
+ self.message_router,
+ self.local_lxmf_destination,
+ )
+ subprocess_result = self_check_mod.check_subprocess_spawn()
+ run_module_result = self_check_mod.check_meshchatx_run_module()
return {
"stack_up": {
@@ -957,6 +983,14 @@ class ReticulumMeshChat:
"status": "ok" if rw_ok else "failed",
"reason": rw_reason,
},
+ "identity_good": identity_result,
+ "imports_good": imports_result,
+ "storage_lock_good": storage_lock_result,
+ "temp_fs_good": temp_fs_result,
+ "public_assets_good": public_assets_result,
+ "lxmf_router_good": lxmf_result,
+ "subprocess_good": subprocess_result,
+ "run_module_good": run_module_result,
"bots_lifecycle": {
"status": "ok" if bots_ok else "failed",
"reason": bots_reason,
@@ -20411,15 +20445,9 @@ def main():
print("================================")
all_passed = True
- labels = {
- "stack_up": "Network Stack ",
- "config_good": "Configuration Integrity",
- "db_good": "Database Connection ",
- "read_write_good": "Storage Read/Write ",
- "bots_lifecycle": "Bot Create/Start/Stop ",
- }
+ from meshchatx.src.backend.self_check import SELF_CHECK_LABELS
- for key, name in labels.items():
+ for key, name in SELF_CHECK_LABELS.items():
check = results.get(key, {"status": "failed", "reason": "No result"})
if check["status"] == "ok":
print(f"[OK] {name}")

diff --git a/meshchatx/src/backend/self_check.py b/meshchatx/src/backend/self_check.py
new file mode 100644
index 00000000..0163a2ed
--- /dev/null
+++ b/meshchatx/src/backend/self_check.py
@@ -0,0 +1,280 @@
+# SPDX-License-Identifier: 0BSD
+
+"""Headless / Settings self-check helpers for critical cross-platform diagnostics."""
+
+from __future__ import annotations
+
+import contextlib
+import importlib
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+from collections.abc import Callable
+from typing import Any
+
+from meshchatx.src.backend import self_check_probe as _self_check_probe # noqa: F401
+
+_CRITICAL_IMPORTS = (
+ "RNS",
+ "LXMF",
+ "lxmfy",
+ "aiohttp",
+ "bcrypt",
+ "cbor2",
+ "bleak",
+ "websockets",
+ "psutil",
+)
+
+_SELF_CHECK_PROBE_MODULE = "meshchatx.src.backend.self_check_probe"
+_MESHCHATX_RUN_MODULE_FLAG = "--meshchatx-run-module"
+
+SELF_CHECK_LABELS = {
+ "stack_up": "Network Stack ",
+ "config_good": "Configuration Integrity",
+ "db_good": "Database Connection ",
+ "read_write_good": "Storage Read/Write ",
+ "identity_good": "Identity Loaded ",
+ "imports_good": "Critical Imports ",
+ "storage_lock_good": "Storage Lock ",
+ "temp_fs_good": "Temp Filesystem ",
+ "public_assets_good": "Public Assets ",
+ "lxmf_router_good": "LXMF Router ",
+ "subprocess_good": "Subprocess Spawn ",
+ "run_module_good": "MeshChatX Run-Module ",
+ "bots_lifecycle": "Bot Create/Start/Stop ",
+}
+
+
+def _status(ok: bool, reason: str = "") -> dict[str, str]:
+ return {"status": "ok" if ok else "failed", "reason": reason or ""}
+
+
+def check_python_runtime() -> dict[str, str]:
+ if sys.version_info < (3, 11):
+ return _status(
+ False,
+ f"Python {sys.version_info.major}.{sys.version_info.minor} is below 3.11",
+ )
+ if not sys.executable or not os.path.exists(sys.executable):
+ return _status(False, f"sys.executable is missing: {sys.executable!r}")
+ return _status(True)
+
+
+def check_critical_imports() -> dict[str, str]:
+ missing: list[str] = []
+ errors: list[str] = []
+ for name in _CRITICAL_IMPORTS:
+ try:
+ importlib.import_module(name)
+ except ModuleNotFoundError:
+ missing.append(name)
+ except Exception as exc:
+ errors.append(f"{name}: {exc}")
+ if missing or errors:
+ parts = []
+ if missing:
+ parts.append("missing " + ", ".join(missing))
+ if errors:
+ parts.append("errors " + " | ".join(errors))
+ return _status(False, ". ".join(parts))
+ return _status(True)
+
+
+def check_identity(identity: Any) -> dict[str, str]:
+ if identity is None:
+ return _status(False, "Identity is not loaded")
+ try:
+ raw = getattr(identity, "hash", None)
+ if raw is None:
+ return _status(False, "Identity has no hash")
+ if isinstance(raw, (bytes, bytearray, memoryview)):
+ hx = bytes(raw).hex()
+ else:
+ hx = str(raw).strip().lower()
+ if len(hx) != 32:
+ return _status(False, f"Identity hash length is {len(hx)}, expected 32")
+ return _status(True)
+ except Exception as exc:
+ return _status(False, f"Identity check failed: {exc}")
+
+
+def check_lxmf_router(message_router: Any, local_destination: Any) -> dict[str, str]:
+ if message_router is None:
+ return _status(False, "LXMF message router is not initialized")
+ if local_destination is None:
+ return _status(False, "Local LXMF destination is not available")
+ try:
+ dest_hash = getattr(local_destination, "hash", None)
+ if dest_hash is None:
+ return _status(False, "Local LXMF destination has no hash")
+ except Exception as exc:
+ return _status(False, f"LXMF destination check failed: {exc}")
+ return _status(True)
+
+
+def check_storage_lock(base_dir: str) -> dict[str, str]:
+ from meshchatx.src.backend.storage_lock import StorageLock, StorageLockError
+
+ if not base_dir or not os.path.isdir(base_dir):
+ return _status(False, "Storage directory does not exist")
+
+ lock_dir = os.path.join(base_dir, ".self_test_storage_lock")
+ try:
+ if os.path.isdir(lock_dir):
+ shutil.rmtree(lock_dir, ignore_errors=True)
+ os.makedirs(lock_dir, exist_ok=True)
+
+ first = StorageLock(lock_dir)
+ first.acquire()
+ second = StorageLock(lock_dir)
+ contested = False
+ try:
+ second.acquire()
+ except StorageLockError:
+ contested = True
+ except Exception as exc:
+ first.release()
+ return _status(False, f"Unexpected lock error: {exc}")
+ else:
+ second.release()
+ first.release()
+ return _status(False, "Second lock acquire should have failed while held")
+
+ if not contested:
+ first.release()
+ return _status(False, "Storage lock did not reject a second holder")
+
+ first.release()
+
+ third = StorageLock(lock_dir)
+ third.acquire()
+ third.release()
+ return _status(True)
+ except Exception as exc:
+ return _status(False, f"Storage lock check failed: {exc}")
+ finally:
+ with contextlib.suppress(Exception):
+ if os.path.isdir(lock_dir):
+ shutil.rmtree(lock_dir, ignore_errors=True)
+
+
+def check_temp_filesystem() -> dict[str, str]:
+ path = None
+ try:
+ fd, path = tempfile.mkstemp(prefix="meshchatx_self_check_")
+ os.close(fd)
+ payload = b"meshchatx-temp-check"
+ with open(path, "wb") as handle:
+ handle.write(payload)
+ with open(path, "rb") as handle:
+ if handle.read() != payload:
+ return _status(False, "Temp file readback mismatch")
+ return _status(True)
+ except Exception as exc:
+ return _status(False, f"Temp filesystem check failed: {exc}")
+ finally:
+ if path and os.path.exists(path):
+ with contextlib.suppress(Exception):
+ os.unlink(path)
+
+
+def check_public_assets(public_path_fn: Callable[[str], str]) -> dict[str, str]:
+ try:
+ root = public_path_fn("")
+ if not root or not os.path.isdir(root):
+ return _status(False, f"Public assets directory missing: {root!r}")
+ index_path = public_path_fn("index.html")
+ if not os.path.isfile(index_path):
+ names = os.listdir(root)
+ if not names:
+ return _status(False, "Public assets directory is empty")
+ return _status(True)
+ except Exception as exc:
+ return _status(False, f"Public assets check failed: {exc}")
+
+
+def _is_frozen_executable() -> bool:
+ return bool(getattr(sys, "frozen", False))
+
+
+def check_meshchatx_run_module() -> dict[str, str]:
+ """Verify ``--meshchatx-run-module`` re-entry used by bots/rnsh on frozen builds."""
+ marker_dir = tempfile.mkdtemp(prefix="meshchatx_run_module_check_")
+ marker = os.path.join(marker_dir, "probe.out")
+ env = os.environ.copy()
+ env["MESHCHATX_SELF_CHECK_PROBE_PATH"] = marker
+ env["PYTHONUNBUFFERED"] = "1"
+ env["MESHCHAT_SKIP_STORAGE_LOCK"] = "1"
+
+ if _is_frozen_executable():
+ cmd = [
+ sys.executable,
+ _MESHCHATX_RUN_MODULE_FLAG,
+ _SELF_CHECK_PROBE_MODULE,
+ "self-check",
+ ]
+ else:
+ cmd = [
+ sys.executable,
+ "-m",
+ "meshchatx.meshchat",
+ _MESHCHATX_RUN_MODULE_FLAG,
+ _SELF_CHECK_PROBE_MODULE,
+ "self-check",
+ ]
+
+ try:
+ result = subprocess.run(
+ cmd,
+ capture_output=True,
+ text=True,
+ timeout=45,
+ check=False,
+ env=env,
+ )
+ if result.returncode != 0:
+ detail = (result.stderr or result.stdout or "").strip()[-500:]
+ return _status(
+ False,
+ f"run-module exited {result.returncode}: {detail or 'no output'}",
+ )
+ if not os.path.isfile(marker):
+ return _status(False, "Probe marker file was not written")
+ with open(marker, encoding="utf-8") as handle:
+ text = handle.read()
+ if "ok" not in text or "self-check" not in text:
+ return _status(False, f"Unexpected probe output: {text!r}")
+ return _status(True)
+ except subprocess.TimeoutExpired:
+ return _status(False, "run-module probe timed out")
+ except Exception as exc:
+ return _status(False, f"run-module check failed: {exc}")
+ finally:
+ with contextlib.suppress(Exception):
+ shutil.rmtree(marker_dir, ignore_errors=True)
+
+
+def check_subprocess_spawn() -> dict[str, str]:
+ """Spawn a short-lived child process (covers Windows CreateProcess / POSIX fork)."""
+ try:
+ result = subprocess.run(
+ [sys.executable, "-c", "print('meshchatx-spawn-ok', flush=True)"],
+ capture_output=True,
+ text=True,
+ timeout=30,
+ check=False,
+ env={**os.environ, "PYTHONUNBUFFERED": "1"},
+ )
+ if result.returncode != 0:
+ return _status(
+ False,
+ f"spawn exited {result.returncode}: {(result.stderr or '')[-300:]}",
+ )
+ if "meshchatx-spawn-ok" not in (result.stdout or ""):
+ return _status(False, f"Unexpected spawn output: {result.stdout!r}")
+ return _status(True)
+ except Exception as exc:
+ return _status(False, f"Subprocess spawn check failed: {exc}")

diff --git a/meshchatx/src/backend/self_check_probe.py b/meshchatx/src/backend/self_check_probe.py
new file mode 100644
index 00000000..805fb460
--- /dev/null
+++ b/meshchatx/src/backend/self_check_probe.py
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: 0BSD
+
+"""Probe module for self-check ``--meshchatx-run-module`` verification."""
+
+from __future__ import annotations
+
+import os
+import sys
+
+
+def main() -> None:
+ marker = os.environ.get("MESHCHATX_SELF_CHECK_PROBE_PATH")
+ if marker:
+ with open(marker, "w", encoding="utf-8") as handle:
+ handle.write("ok\n")
+ handle.write(" ".join(sys.argv[1:]))
+ handle.write("\n")
+ print("meshchatx-self-check-probe", *sys.argv[1:], flush=True)
+
+
+if __name__ == "__main__":
+ main()

diff --git a/meshchatx/src/frontend/components/settings/SettingsPage.vue b/meshchatx/src/frontend/components/settings/SettingsPage.vue
index 7079b4d1..2ae70621 100644
--- a/meshchatx/src/frontend/components/settings/SettingsPage.vue
+++ b/meshchatx/src/frontend/components/settings/SettingsPage.vue
@@ -3044,37 +3044,27 @@ export default {
if (!this.selfTestResults) {
return [];
}
+ const r = this.selfTestResults;
+ const item = (key, labelKey) => ({
+ key,
+ label: this.$t(labelKey),
+ passed: r[key]?.status === "ok",
+ reason: r[key]?.reason || "",
+ });
return [
- {
- key: "stack_up",
- label: this.$t("selftest.stack_up"),
- passed: this.selfTestResults.stack_up.status === "ok",
- reason: this.selfTestResults.stack_up.reason,
- },
- {
- key: "config_good",
- label: this.$t("selftest.config_good"),
- passed: this.selfTestResults.config_good.status === "ok",
- reason: this.selfTestResults.config_good.reason,
- },
- {
- key: "db_good",
- label: this.$t("selftest.db_good"),
- passed: this.selfTestResults.db_good.status === "ok",
- reason: this.selfTestResults.db_good.reason,
- },
- {
- key: "read_write_good",
- label: this.$t("selftest.read_write"),
- passed: this.selfTestResults.read_write_good.status === "ok",
- reason: this.selfTestResults.read_write_good.reason,
- },
- {
- key: "bots_lifecycle",
- label: this.$t("selftest.bots_lifecycle"),
- passed: this.selfTestResults.bots_lifecycle?.status === "ok",
- reason: this.selfTestResults.bots_lifecycle?.reason || "",
- },
+ item("stack_up", "selftest.stack_up"),
+ item("config_good", "selftest.config_good"),
+ item("db_good", "selftest.db_good"),
+ item("read_write_good", "selftest.read_write"),
+ item("identity_good", "selftest.identity_good"),
+ item("imports_good", "selftest.imports_good"),
+ item("storage_lock_good", "selftest.storage_lock_good"),
+ item("temp_fs_good", "selftest.temp_fs_good"),
+ item("public_assets_good", "selftest.public_assets_good"),
+ item("lxmf_router_good", "selftest.lxmf_router_good"),
+ item("subprocess_good", "selftest.subprocess_good"),
+ item("run_module_good", "selftest.run_module_good"),
+ item("bots_lifecycle", "selftest.bots_lifecycle"),
];
},
allSelfTestChecksPassed() {
@@ -3156,6 +3146,14 @@ export default {
config_good: { status: "failed", reason: e.message || String(e) },
db_good: { status: "failed", reason: e.message || String(e) },
read_write_good: { status: "failed", reason: e.message || String(e) },
+ identity_good: { status: "failed", reason: e.message || String(e) },
+ imports_good: { status: "failed", reason: e.message || String(e) },
+ storage_lock_good: { status: "failed", reason: e.message || String(e) },
+ temp_fs_good: { status: "failed", reason: e.message || String(e) },
+ public_assets_good: { status: "failed", reason: e.message || String(e) },
+ lxmf_router_good: { status: "failed", reason: e.message || String(e) },
+ subprocess_good: { status: "failed", reason: e.message || String(e) },
+ run_module_good: { status: "failed", reason: e.message || String(e) },
bots_lifecycle: { status: "failed", reason: e.message || String(e) },
};
} finally {

diff --git a/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js b/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
index 2b9768e9..135e0fd1 100644
--- a/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
+++ b/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
@@ -107,6 +107,12 @@ export const CORE_SETTINGS_SECTION_KEYWORDS = {
"selftest.description",
"bot",
"bots",
+ "identity",
+ "imports",
+ "storage lock",
+ "subprocess",
+ "run-module",
+ "lxmf",
],
desktop: [
"Desktop",

diff --git a/meshchatx/src/frontend/locales/de.json b/meshchatx/src/frontend/locales/de.json
index d668a3b5..c678e6cc 100644
--- a/meshchatx/src/frontend/locales/de.json
+++ b/meshchatx/src/frontend/locales/de.json
@@ -704,6 +704,14 @@
"config_good": "Konfigurationsintegrität",
"db_good": "Datenbankverbindung",
"read_write": "Speicher Lese/Schreib",
+ "identity_good": "Identity Loaded",
+ "imports_good": "Critical Imports",
+ "storage_lock_good": "Storage Lock",
+ "temp_fs_good": "Temp Filesystem",
+ "public_assets_good": "Public Assets",
+ "lxmf_router_good": "LXMF Router",
+ "subprocess_good": "Subprocess Spawn",
+ "run_module_good": "MeshChatX Run-Module",
"bots_lifecycle": "Bot Erstellen / Starten / Stoppen / Löschen",
"passed": "Bestanden",
"failed": "Fehlgeschlagen",

diff --git a/meshchatx/src/frontend/locales/en.json b/meshchatx/src/frontend/locales/en.json
index d4745738..f2ddd099 100644
--- a/meshchatx/src/frontend/locales/en.json
+++ b/meshchatx/src/frontend/locales/en.json
@@ -697,13 +697,21 @@
},
"selftest": {
"title": "System Self-Test",
- "description": "Run diagnostic checks to ensure the core network stack, database, configurations, storage permissions, and bot lifecycle are functional.",
+ "description": "Run diagnostic checks for the network stack, database, identity, critical imports, storage lock, temp filesystem, public assets, LXMF router, subprocess spawn, run-module re-entry, and bot lifecycle.",
"run_test_btn": "Run Diagnostics",
"running": "Running Diagnostics...",
"stack_up": "Network Stack",
"config_good": "Configuration Integrity",
"db_good": "Database Connection",
"read_write": "Storage Read/Write",
+ "identity_good": "Identity Loaded",
+ "imports_good": "Critical Imports",
+ "storage_lock_good": "Storage Lock",
+ "temp_fs_good": "Temp Filesystem",
+ "public_assets_good": "Public Assets",
+ "lxmf_router_good": "LXMF Router",
+ "subprocess_good": "Subprocess Spawn",
+ "run_module_good": "MeshChatX Run-Module",
"bots_lifecycle": "Bot Create / Start / Stop / Delete",
"passed": "Passed",
"failed": "Failed",

diff --git a/meshchatx/src/frontend/locales/es.json b/meshchatx/src/frontend/locales/es.json
index 0cdf9250..919a0b21 100644
--- a/meshchatx/src/frontend/locales/es.json
+++ b/meshchatx/src/frontend/locales/es.json
@@ -704,6 +704,14 @@
"config_good": "Integridad de configuración",
"db_good": "Conexión a la base de datos",
"read_write": "Lectura/escritura de almacenamiento",
+ "identity_good": "Identity Loaded",
+ "imports_good": "Critical Imports",
+ "storage_lock_good": "Storage Lock",
+ "temp_fs_good": "Temp Filesystem",
+ "public_assets_good": "Public Assets",
+ "lxmf_router_good": "LXMF Router",
+ "subprocess_good": "Subprocess Spawn",
+ "run_module_good": "MeshChatX Run-Module",
"bots_lifecycle": "Bot Crear / Iniciar / Detener / Eliminar",
"passed": "Aprobado",
"failed": "Fallido",

diff --git a/meshchatx/src/frontend/locales/fi.json b/meshchatx/src/frontend/locales/fi.json
index ced61df2..88af7e71 100644
--- a/meshchatx/src/frontend/locales/fi.json
+++ b/meshchatx/src/frontend/locales/fi.json
@@ -704,6 +704,14 @@
"config_good": "Asetusten eheys",
"db_good": "Tietokantayhteys",
"read_write": "Tallennustilan luku/kirjoitus",
+ "identity_good": "Identity Loaded",
+ "imports_good": "Critical Imports",
+ "storage_lock_good": "Storage Lock",
+ "temp_fs_good": "Temp Filesystem",
+ "public_assets_good": "Public Assets",
+ "lxmf_router_good": "LXMF Router",
+ "subprocess_good": "Subprocess Spawn",
+ "run_module_good": "MeshChatX Run-Module",
"bots_lifecycle": "Botin luonti / käynnistys / pysäytys / poisto",
"passed": "Hyväksytty",
"failed": "Epäonnistunut",

diff --git a/meshchatx/src/frontend/locales/fr.json b/meshchatx/src/frontend/locales/fr.json
index c561309d..96425b11 100644
--- a/meshchatx/src/frontend/locales/fr.json
+++ b/meshchatx/src/frontend/locales/fr.json
@@ -704,6 +704,14 @@
"config_good": "Intégrité de la configuration",
"db_good": "Connexion à la base de données",
"read_write": "Lecture/Écriture stockage",
+ "identity_good": "Identity Loaded",
+ "imports_good": "Critical Imports",
+ "storage_lock_good": "Storage Lock",
+ "temp_fs_good": "Temp Filesystem",
+ "public_assets_good": "Public Assets",
+ "lxmf_router_good": "LXMF Router",
+ "subprocess_good": "Subprocess Spawn",
+ "run_module_good": "MeshChatX Run-Module",
"bots_lifecycle": "Bot Créer / Démarrer / Arrêter / Supprimer",
"passed": "Réussi",
"failed": "Échoué",

diff --git a/meshchatx/src/frontend/locales/it.json b/meshchatx/src/frontend/locales/it.json
index ef5adb58..5727000f 100644
--- a/meshchatx/src/frontend/locales/it.json
+++ b/meshchatx/src/frontend/locales/it.json
@@ -704,6 +704,14 @@
"config_good": "Integrità Configurazione",
"db_good": "Connessione Database",
"read_write": "Lettura/Scrittura Archivio",
+ "identity_good": "Identity Loaded",
+ "imports_good": "Critical Imports",
+ "storage_lock_good": "Storage Lock",
+ "temp_fs_good": "Temp Filesystem",
+ "public_assets_good": "Public Assets",
+ "lxmf_router_good": "LXMF Router",
+ "subprocess_good": "Subprocess Spawn",
+ "run_module_good": "MeshChatX Run-Module",
"bots_lifecycle": "Bot Crea / Avvia / Ferma / Elimina",
"passed": "Superato",
"failed": "Fallito",

diff --git a/meshchatx/src/frontend/locales/nl.json b/meshchatx/src/frontend/locales/nl.json
index 64bb8e87..2733a83b 100644
--- a/meshchatx/src/frontend/locales/nl.json
+++ b/meshchatx/src/frontend/locales/nl.json
@@ -704,6 +704,14 @@
"config_good": "Configuratie-integriteit",
"db_good": "Databaseverbinding",
"read_write": "Opslag Lezen/Schrijven",
+ "identity_good": "Identity Loaded",
+ "imports_good": "Critical Imports",
+ "storage_lock_good": "Storage Lock",
+ "temp_fs_good": "Temp Filesystem",
+ "public_assets_good": "Public Assets",
+ "lxmf_router_good": "LXMF Router",
+ "subprocess_good": "Subprocess Spawn",
+ "run_module_good": "MeshChatX Run-Module",
"bots_lifecycle": "Bot Aanmaken / Starten / Stoppen / Verwijderen",
"passed": "Geslaagd",
"failed": "Mislukt",

diff --git a/meshchatx/src/frontend/locales/ru.json b/meshchatx/src/frontend/locales/ru.json
index 7f82cc25..5b609334 100644
--- a/meshchatx/src/frontend/locales/ru.json
+++ b/meshchatx/src/frontend/locales/ru.json
@@ -704,6 +704,14 @@
"config_good": "Целостность конфигурации",
"db_good": "Подключение к базе данных",
"read_write": "Чтение/запись в хранилище",
+ "identity_good": "Identity Loaded",
+ "imports_good": "Critical Imports",
+ "storage_lock_good": "Storage Lock",
+ "temp_fs_good": "Temp Filesystem",
+ "public_assets_good": "Public Assets",
+ "lxmf_router_good": "LXMF Router",
+ "subprocess_good": "Subprocess Spawn",
+ "run_module_good": "MeshChatX Run-Module",
"bots_lifecycle": "Бот: создать / запустить / остановить / удалить",
"passed": "Успешно",
"failed": "Ошибка",

diff --git a/meshchatx/src/frontend/locales/zh.json b/meshchatx/src/frontend/locales/zh.json
index 0c0fd2c8..ab99d086 100644
--- a/meshchatx/src/frontend/locales/zh.json
+++ b/meshchatx/src/frontend/locales/zh.json
@@ -704,6 +704,14 @@
"config_good": "配置完整性",
"db_good": "数据库连接",
"read_write": "存储读/写",
+ "identity_good": "Identity Loaded",
+ "imports_good": "Critical Imports",
+ "storage_lock_good": "Storage Lock",
+ "temp_fs_good": "Temp Filesystem",
+ "public_assets_good": "Public Assets",
+ "lxmf_router_good": "LXMF Router",
+ "subprocess_good": "Subprocess Spawn",
+ "run_module_good": "MeshChatX Run-Module",
"bots_lifecycle": "机器人 创建 / 启动 / 停止 / 删除",
"passed": "已通过",
"failed": "已失败",

diff --git a/tests/backend/api_json_contract_schemas.py b/tests/backend/api_json_contract_schemas.py
index b9d54d4d..da8f5236 100644
--- a/tests/backend/api_json_contract_schemas.py
+++ b/tests/backend/api_json_contract_schemas.py
@@ -204,6 +204,14 @@ SELF_TEST_SCHEMA: dict = {
"config_good",
"db_good",
"read_write_good",
+ "identity_good",
+ "imports_good",
+ "storage_lock_good",
+ "temp_fs_good",
+ "public_assets_good",
+ "lxmf_router_good",
+ "subprocess_good",
+ "run_module_good",
"bots_lifecycle",
],
"properties": {
@@ -211,6 +219,14 @@ SELF_TEST_SCHEMA: dict = {
"config_good": SELF_TEST_STATUS_ITEM_SCHEMA,
"db_good": SELF_TEST_STATUS_ITEM_SCHEMA,
"read_write_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "identity_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "imports_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "storage_lock_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "temp_fs_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "public_assets_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "lxmf_router_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "subprocess_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "run_module_good": SELF_TEST_STATUS_ITEM_SCHEMA,
"bots_lifecycle": SELF_TEST_STATUS_ITEM_SCHEMA,
},
"additionalProperties": False,

diff --git a/tests/backend/test_self_check.py b/tests/backend/test_self_check.py
new file mode 100644
index 00000000..d34790f7
--- /dev/null
+++ b/tests/backend/test_self_check.py
@@ -0,0 +1,90 @@
+# SPDX-License-Identifier: 0BSD
+
+"""Unit tests for cross-platform self-check helpers."""
+
+from __future__ import annotations
+
+from types import SimpleNamespace
+
+from meshchatx.src.backend import self_check
+
+
+def test_check_python_runtime_ok():
+ assert self_check.check_python_runtime()["status"] == "ok"
+
+
+def test_check_critical_imports_ok():
+ assert self_check.check_critical_imports()["status"] == "ok"
+
+
+def test_check_identity_ok():
+ identity = SimpleNamespace(hash=b"\x11" * 16)
+ assert self_check.check_identity(identity)["status"] == "ok"
+
+
+def test_check_identity_missing():
+ result = self_check.check_identity(None)
+ assert result["status"] == "failed"
+ assert "not loaded" in result["reason"].lower()
+
+
+def test_check_identity_bad_hash_length():
+ identity = SimpleNamespace(hash=b"\x11" * 8)
+ result = self_check.check_identity(identity)
+ assert result["status"] == "failed"
+
+
+def test_check_lxmf_router_ok():
+ router = object()
+ dest = SimpleNamespace(hash=b"\xaa" * 16)
+ assert self_check.check_lxmf_router(router, dest)["status"] == "ok"
+
+
+def test_check_lxmf_router_missing():
+ result = self_check.check_lxmf_router(None, None)
+ assert result["status"] == "failed"
+
+
+def test_check_storage_lock_ok(tmp_path):
+ result = self_check.check_storage_lock(str(tmp_path))
+ assert result["status"] == "ok", result["reason"]
+ assert not (tmp_path / ".self_test_storage_lock").exists()
+
+
+def test_check_temp_filesystem_ok():
+ assert self_check.check_temp_filesystem()["status"] == "ok"
+
+
+def test_check_public_assets_ok(tmp_path):
+ (tmp_path / "index.html").write_text("<html></html>", encoding="utf-8")
+
+ def public_path(name=""):
+ return str(tmp_path / name) if name else str(tmp_path)
+
+ assert self_check.check_public_assets(public_path)["status"] == "ok"
+
+
+def test_check_public_assets_missing(tmp_path):
+ missing = tmp_path / "nope"
+
+ def public_path(name=""):
+ return str(missing / name) if name else str(missing)
+
+ result = self_check.check_public_assets(public_path)
+ assert result["status"] == "failed"
+
+
+def test_check_subprocess_spawn_ok():
+ assert self_check.check_subprocess_spawn()["status"] == "ok"
+
+
+def test_check_meshchatx_run_module_ok():
+ result = self_check.check_meshchatx_run_module()
+ assert result["status"] == "ok", result["reason"]
+
+
+def test_self_check_labels_cover_schema_keys():
+ from tests.backend.api_json_contract_schemas import SELF_TEST_SCHEMA
+
+ required = set(SELF_TEST_SCHEMA["required"])
+ assert set(self_check.SELF_CHECK_LABELS) == required

diff --git a/tests/backend/test_self_check_cli.py b/tests/backend/test_self_check_cli.py
index e06cbc24..a72130ff 100644
--- a/tests/backend/test_self_check_cli.py
+++ b/tests/backend/test_self_check_cli.py
@@ -75,15 +75,15 @@ def mock_rns():
}
+def _ok_results():
+ from meshchatx.src.backend.self_check import SELF_CHECK_LABELS
+
+ return {key: {"status": "ok", "reason": ""} for key in SELF_CHECK_LABELS}
+
+
def test_self_check_cli_success(mock_rns, temp_dir):
"""Test that self-check CLI argument prints results and exits with 0 on success."""
- mock_results = {
- "stack_up": {"status": "ok", "reason": ""},
- "config_good": {"status": "ok", "reason": ""},
- "db_good": {"status": "ok", "reason": ""},
- "read_write_good": {"status": "ok", "reason": ""},
- "bots_lifecycle": {"status": "ok", "reason": ""},
- }
+ mock_results = _ok_results()
with (
patch("meshchatx.meshchat.ReticulumMeshChat") as mock_app_class,
@@ -104,15 +104,14 @@ def test_self_check_cli_success(mock_rns, temp_dir):
def test_self_check_cli_failure(mock_rns, temp_dir):
"""Test that self-check CLI argument prints results and exits with 1 on failure."""
- mock_results = {
- "stack_up": {
- "status": "failed",
- "reason": "Reticulum stack is not initialized",
- },
- "config_good": {"status": "ok", "reason": ""},
- "db_good": {"status": "failed", "reason": "Database check failed"},
- "read_write_good": {"status": "ok", "reason": ""},
- "bots_lifecycle": {"status": "ok", "reason": ""},
+ mock_results = _ok_results()
+ mock_results["stack_up"] = {
+ "status": "failed",
+ "reason": "Reticulum stack is not initialized",
+ }
+ mock_results["db_good"] = {
+ "status": "failed",
+ "reason": "Database check failed",
}
with (
@@ -134,13 +133,7 @@ def test_self_check_cli_failure(mock_rns, temp_dir):
def test_self_check_env_var_success(mock_rns, temp_dir):
"""Test that MESHCHAT_SELF_CHECK env var triggers self-check and exits with 0 on success."""
- mock_results = {
- "stack_up": {"status": "ok", "reason": ""},
- "config_good": {"status": "ok", "reason": ""},
- "db_good": {"status": "ok", "reason": ""},
- "read_write_good": {"status": "ok", "reason": ""},
- "bots_lifecycle": {"status": "ok", "reason": ""},
- }
+ mock_results = _ok_results()
env = {
"MESHCHAT_SELF_CHECK": "true",

diff --git a/tests/e2e/smoke.spec.js b/tests/e2e/smoke.spec.js
index 27c1e011..9aee5dd4 100644
--- a/tests/e2e/smoke.spec.js
+++ b/tests/e2e/smoke.spec.js
@@ -15,11 +15,25 @@ test.describe("MeshChatX E2E (Vite + Python backend)", () => {
const res = await request.get("/api/v1/self-test");
expect(res.ok()).toBeTruthy();
const body = await res.json();
- expect(body.stack_up.status).toBe("ok");
- expect(body.config_good.status).toBe("ok");
- expect(body.db_good.status).toBe("ok");
- expect(body.read_write_good.status).toBe("ok");
- expect(body.bots_lifecycle.status).toBe("ok");
+ const keys = [
+ "stack_up",
+ "config_good",
+ "db_good",
+ "read_write_good",
+ "identity_good",
+ "imports_good",
+ "storage_lock_good",
+ "temp_fs_good",
+ "public_assets_good",
+ "lxmf_router_good",
+ "subprocess_good",
+ "run_module_good",
+ "bots_lifecycle",
+ ];
+ for (const key of keys) {
+ expect(body[key], key).toBeDefined();
+ expect(body[key].status, key).toBe("ok");
+ }
});
test("backend /api/v1/app/info returns version JSON (direct backend)", async ({ request }) => {


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────